package com.xzzz.irda.codegen.utils;

import cn.hutool.core.util.StrUtil;
import com.xzzz.irda.codegen.pojo.ColumnDO;
import com.xzzz.irda.codegen.pojo.TableDO;
import lombok.extern.slf4j.Slf4j;
import org.apache.velocity.Template;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.Velocity;

import java.io.IOException;
import java.io.InputStreamReader;
import java.io.StringWriter;
import java.nio.charset.StandardCharsets;
import java.util.*;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

/**
 * 代码生成工具类
 *
 * @author xzzz
 * @version 0.0.1
 */
@Slf4j
public class GenUtils {

    private static final Properties PROPERTIES = new Properties();
    /**
     * 公共模板，与生成模板文件的路径的映射
     */
    private static final Map<String, String> COMMON_PATH_MAP = new HashMap<>();
    /**
     * 业务模板，与生成模板文件的路径的映射
     */
    private static final Map<String, String> SERVER_MAP = new HashMap<>();
    private static final Map<String, String> MAPPER_MAP = new HashMap<>();
    /**
     * 公用的模板数据。
     */
    private static final Map<String, Object> COMMON_DATA_MAP = new HashMap<>();
    /**
     * 业务模板数据。
     */
    private static final Map<String, Object> BIZ_DATA_MAP = new HashMap<>();

    static {
        try {
            PROPERTIES.load(new InputStreamReader(Objects.requireNonNull(GenUtils.class.getClassLoader().getResourceAsStream("config/generator.properties")), StandardCharsets.UTF_8));
        } catch (IOException e) {
            log.error("加载 generator.properties 出错！");
        }

        // "project" -> "backendtest"
        String project = PROPERTIES.getProperty("project");
        // 项目的包前缀, com.xzzz
        String packageBase = PROPERTIES.getProperty("packageBase");
        String packageBaseReplace = PROPERTIES.getProperty("packageBase").replaceAll("\\.","/");
        // 项目的启动类类名前缀 Backendtest
        String projectClass = StrUtil.upperFirst(project);
        // 包路径  示例：将 com.xzzz.project 转成  com/xzzz/project
        String packageAll = packageBase.concat(".").concat(project).replaceAll("\\.", "/");
        // 项目的 Java 路径
        String projectJavaPath = "src/main/java";
        // 项目的 Java 路径 + 包路径 示例：src/main/java/com/xzzz/project
        String projectJavaPackagePath = projectJavaPath.concat("/").concat(packageAll);

        COMMON_PATH_MAP.put("template/pom.xml.vm",   String.format("%s/pom.xml",    project));
        COMMON_PATH_MAP.put("template/gitignore.vm", String.format("%s/.gitignore", project));
        COMMON_PATH_MAP.put("template/README.md.vm", String.format("%s/README.md",  project));

        COMMON_PATH_MAP.put("template/web/Application.java.vm" ,  String.format("%s/%s-web/%s/web/%sApplication.java" , project, project, projectJavaPackagePath, projectClass));
        COMMON_PATH_MAP.put("template/web/WebMvcConfig.java.vm" , String.format("%s/%s-web/%s/web/common/config/WebMvcConfig.java" , project, project, projectJavaPackagePath));

        // web 层公共代码
        String projectResourcesPath = "src/main/resources";
        COMMON_PATH_MAP.put("template/web/pom.xml.vm" ,            String.format("%s/%s-web/pom.xml" , project, project));
        COMMON_PATH_MAP.put("template/web/application.yml.vm" ,    String.format("%s/%s-web/%s/config/application.yml" ,    project, project, projectResourcesPath));
        COMMON_PATH_MAP.put("template/web/application-dev.yml.vm", String.format("%s/%s-web/%s/config/application-dev.yml", project, project, projectResourcesPath));
        COMMON_PATH_MAP.put("template/web/logback-spring.xml.vm",  String.format("%s/%s-web/%s/config/logback-spring.xml",  project, project, projectResourcesPath));

        // common-base 代码
        COMMON_PATH_MAP.put("template/common-base/pom.xml.vm",                      String.format("%s/common-base/pom.xml", project));
        COMMON_PATH_MAP.put("template/common-base/spring.factories.vm",             String.format("%s/common-base/%s/META-INF/spring.factories", project,projectResourcesPath));

        COMMON_PATH_MAP.put("template/common-base/CoreConfig.java.vm",              String.format("%s/common-base/%s/%s/common/core/CoreConfig.java",              project,projectJavaPath,packageBaseReplace));
        COMMON_PATH_MAP.put("template/common-base/AbstractExceptionAdvice.java.vm", String.format("%s/common-base/%s/%s/common/core/exception/AbstractExceptionAdvice.java", project,projectJavaPath,packageBaseReplace));
        COMMON_PATH_MAP.put("template/common-base/ExceptionAdviceByGlobal.java.vm", String.format("%s/common-base/%s/%s/common/core/exception/ExceptionAdviceByGlobal.java", project,projectJavaPath,packageBaseReplace));
        COMMON_PATH_MAP.put("template/common-base/ExceptionAdviceByXz.java.vm",     String.format("%s/common-base/%s/%s/common/core/exception/ExceptionAdviceByXz.java",     project,projectJavaPath,packageBaseReplace));
        COMMON_PATH_MAP.put("template/common-base/XzAbstractException.java.vm",     String.format("%s/common-base/%s/%s/common/core/exception/XzAbstractException.java",     project,projectJavaPath,packageBaseReplace));
        COMMON_PATH_MAP.put("template/common-base/XzException400.java.vm",          String.format("%s/common-base/%s/%s/common/core/exception/XzException400.java",          project,projectJavaPath,packageBaseReplace));
        COMMON_PATH_MAP.put("template/common-base/XzException404.java.vm",          String.format("%s/common-base/%s/%s/common/core/exception/XzException404.java",          project,projectJavaPath,packageBaseReplace));
        COMMON_PATH_MAP.put("template/common-base/XzException500.java.vm",          String.format("%s/common-base/%s/%s/common/core/exception/XzException500.java",          project,projectJavaPath,packageBaseReplace));
        COMMON_PATH_MAP.put("template/common-base/aspect/PageAspect.java.vm",       String.format("%s/common-base/%s/%s/common/core/aspect/PageAspect.java",       project,projectJavaPath,packageBaseReplace));
        COMMON_PATH_MAP.put("template/common-base/aspect/Pages.java.vm",            String.format("%s/common-base/%s/%s/common/core/aspect/Pages.java",            project,projectJavaPath,packageBaseReplace));
        COMMON_PATH_MAP.put("template/common-base/pojo/AbstractEntity.java.vm",     String.format("%s/common-base/%s/%s/common/core/pojo/AbstractEntity.java",     project,projectJavaPath,packageBaseReplace));
        COMMON_PATH_MAP.put("template/common-base/pojo/AbstractPOJO.java.vm",       String.format("%s/common-base/%s/%s/common/core/pojo/AbstractPOJO.java",       project,projectJavaPath,packageBaseReplace));
        COMMON_PATH_MAP.put("template/common-base/pojo/PageReq.java.vm",            String.format("%s/common-base/%s/%s/common/core/pojo/PageReq.java",            project,projectJavaPath,packageBaseReplace));
        COMMON_PATH_MAP.put("template/common-base/pojo/PageRes.java.vm",            String.format("%s/common-base/%s/%s/common/core/pojo/PageRes.java",            project,projectJavaPath,packageBaseReplace));
        COMMON_PATH_MAP.put("template/common-base/pojo/R.java.vm",                  String.format("%s/common-base/%s/%s/common/core/pojo/R.java",                  project,projectJavaPath,packageBaseReplace));
        COMMON_PATH_MAP.put("template/common-base/pojo/RCode.java.vm",              String.format("%s/common-base/%s/%s/common/core/pojo/RCode.java",              project,projectJavaPath,packageBaseReplace));
        COMMON_PATH_MAP.put("template/common-base/util/BeanUtil.java.vm",           String.format("%s/common-base/%s/%s/common/core/util/BeanUtil.java",           project,projectJavaPath,packageBaseReplace));
        COMMON_PATH_MAP.put("template/common-base/util/DateUtils.java.vm",          String.format("%s/common-base/%s/%s/common/core/util/DateUtils.java",          project,projectJavaPath,packageBaseReplace));
        COMMON_PATH_MAP.put("template/common-base/util/HttpUtil.java.vm",           String.format("%s/common-base/%s/%s/common/core/util/HttpUtil.java",           project,projectJavaPath,packageBaseReplace));
        COMMON_PATH_MAP.put("template/common-base/util/JsonUtil.java.vm",           String.format("%s/common-base/%s/%s/common/core/util/JsonUtil.java",           project,projectJavaPath,packageBaseReplace));
        COMMON_PATH_MAP.put("template/common-base/util/PrimaryKeyUtil.java.vm",     String.format("%s/common-base/%s/%s/common/core/util/PrimaryKeyUtil.java",     project,projectJavaPath,packageBaseReplace));
        COMMON_PATH_MAP.put("template/common-base/util/SpringUtil.java.vm",         String.format("%s/common-base/%s/%s/common/core/util/SpringUtil.java",         project,projectJavaPath,packageBaseReplace));
        COMMON_PATH_MAP.put("template/common-base/util/ServletUtil.java.vm",        String.format("%s/common-base/%s/%s/common/core/util/ServletUtil.java",        project,projectJavaPath,packageBaseReplace));

        // common-db 代码
        COMMON_PATH_MAP.put("template/common-db/pom.xml.vm", String.format("%s/common-db/pom.xml", project));

        // common-redis 代码

        // web 业务代码
        MAPPER_MAP.put("template/mapper/Mapper.java.vm",    project + "/" + project + "-web/" + projectJavaPackagePath + "/web/mapper/%sMapper.java");
        SERVER_MAP.put("template/server/Entity.java.vm",    project + "/" + project + "-web/" + projectJavaPackagePath + "/web/server/%s/pojo/%sEntity.java");
        SERVER_MAP.put("template/server/PageReq.java.vm",   project + "/" + project + "-web/" + projectJavaPackagePath + "/web/server/%s/pojo/%sPageReq.java");
        SERVER_MAP.put("template/server/Res.java.vm",       project + "/" + project + "-web/" + projectJavaPackagePath + "/web/server/%s/pojo/%sRes.java");
        SERVER_MAP.put("template/server/Service.java.vm",   project + "/" + project + "-web/" + projectJavaPackagePath + "/web/server/%s/%sService.java");


        // "project" -> "backendtest"
        COMMON_DATA_MAP.put("project", project);
        // "module" -> "backendtest"
        COMMON_DATA_MAP.put("module", project);
        // "projectZh" -> "测试项目"
        COMMON_DATA_MAP.put("projectZh", PROPERTIES.getProperty("projectZh"));
        // "author" -> "xzzz"
        COMMON_DATA_MAP.put("author", PROPERTIES.getProperty("author"));
        // "version" -> "0.0.1"
        COMMON_DATA_MAP.put("version", PROPERTIES.getProperty("version"));
        // "dbHost" -> "111.229.200.229"
        COMMON_DATA_MAP.put("dbHost", PROPERTIES.getProperty("dbHost"));
        // "dbPort" -> "3306"
        COMMON_DATA_MAP.put("dbPort", PROPERTIES.getProperty("dbPort"));
        // "dbName" -> "fs_prod"
        COMMON_DATA_MAP.put("dbName", PROPERTIES.getProperty("dbName"));
        // "dbUsername" -> "root"
        COMMON_DATA_MAP.put("dbUsername", PROPERTIES.getProperty("dbUsername"));
        // "dbPassword" -> "jasmine888"
        COMMON_DATA_MAP.put("dbPassword", PROPERTIES.getProperty("dbPassword"));
        // "packageBase" -> "com.xzzz"
        COMMON_DATA_MAP.put("packageBase", packageBase);
        // package -> com.xzzz.backendtest
        COMMON_DATA_MAP.put("package", packageBase.concat(".").concat(project));
        // "projectClass" -> "Backendtest"
        COMMON_DATA_MAP.put("projectClass", projectClass);

        BIZ_DATA_MAP.putAll(COMMON_DATA_MAP);
    }

    /**
     * 生成代码
     *
     * @param tableDOList tableDO 列表信息
     * @param zip         压缩输出流
     */
    public static void generatorCode(List<TableDO> tableDOList, ZipOutputStream zip) {

        // 公共模板渲染
        renderCommonTemplate(zip);

        for (TableDO table : tableDOList) {
            boolean hasBigDecimal;

            // 填充 tableDO 中的数据
            hasBigDecimal = fillTableDoData(table);

            // 业务模板渲染
            renderBizTemplate(table, hasBigDecimal, zip);
        }

    }

    /**
     * 公共模板渲染
     *
     * @param zip ZipOutputStream
     */
    private static void renderCommonTemplate(ZipOutputStream zip) {
        VelocityContext context = new VelocityContext(COMMON_DATA_MAP);

        // 设置 velocity 资源加载器   in favor of
        Properties prop = new Properties();
        prop.put("resource.loader.file.class", "org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader");
        Velocity.init(prop);

        for (String key : COMMON_PATH_MAP.keySet()) {
            // 渲染模板
            StringWriter sw = new StringWriter();
            Template tpl = Velocity.getTemplate(key, "UTF-8");
            tpl.merge(context, sw);

            try {
                // 添加到zip
                zip.putNextEntry(new ZipEntry(COMMON_PATH_MAP.get(key)));
                IoUtil.write(sw.toString(), zip, "UTF-8");
                IoUtil.close(sw);
                zip.closeEntry();
            } catch (IOException e) {
                throw new RuntimeException("公共模板渲染失败", e);
            }
        }
    }

    /**
     * 给 TableDO 填充数据。
     *
     * @param table TableDO
     * @return boolean 是否有 BigDecimal 类型
     */
    private static boolean fillTableDoData(TableDO table) {
        boolean hasBigDecimal = false;

        String tableName = table.getTableName();
        String[] strArr = tableName.split("_");
        table.setModuleName(tableName.replace("_", "").toLowerCase());

        table.setClassName(StrUtil.upperFirst(columnToJava(tableName)));
        table.setAttrClassName(StrUtil.lowerFirst(table.getClassName()));

        //列信息
        for (ColumnDO column : table.getColumns()) {
            if (StrUtil.isEmpty(column.getColumnDefault())) {
                column.setColumnDefault("");
                String egValue = column.getEgValue();
                if(StrUtil.isNotEmpty(egValue)){
                    column.setColumnDefault(egValue);
                }
            }

            if (null != column.getColumnComment()) {
                column.setExtra(column.getColumnComment());
                column.setColumnComment(column.getColumnComment().split("：")[0]);
            }

            if (null == column.getExtra()) {
                column.setExtra("");
            }

            //列名转换成Java属性名
            String attrName = columnToJava(column.getColumnName());
            // column.setAttrName(attrName);
            column.setAttrName(StrUtil.lowerFirst(attrName));

            String dataType = column.getDataType();

            // 列的数据类型，转换成 Java 类型
            String attrType = PROPERTIES.getProperty(dataType, "unknowType");
            column.setAttrType(attrType);
            if (!hasBigDecimal && "BigDecimal".equals(attrType)) {
                hasBigDecimal = true;
            }

            if("Long".equals(attrType)){
                int dataLength = Integer.parseInt(column.getDataLength());
                if(dataLength >= 19){
                    column.setMaxValue("9223372036854775807L");
                }else{
                    column.setMaxValue(createMaxValue(dataLength));
                }
            }

            if("Integer".equals(attrType)){
                int dataLength = Integer.parseInt(column.getDataLength());
                if(dataLength >= 10){
                    column.setMaxValue("2147483647");
                }else{
                    column.setMaxValue(createMaxValue(dataLength));
                }
            }
        }
        return hasBigDecimal;
    }

    /**
     * 拼接最大值
     *
     * @param dataLength 数据的长度
     * @return String
     */
    private static String createMaxValue(int dataLength) {
        StringBuilder builder = new StringBuilder();
        for (int i = 0; i < dataLength; i++){
            builder.append(9);
        }
        return builder.append("L").toString();
    }

    /**
     * 公共模板渲染
     *
     * @param zip ZipOutputStream
     */
    private static void renderBizTemplate(TableDO table, boolean hasBigDecimal, ZipOutputStream zip) {
        // 填充模板数据
        BIZ_DATA_MAP.put("moduleName", table.getModuleName());
        BIZ_DATA_MAP.put("tableName", table.getTableName());
        BIZ_DATA_MAP.put("tableComment", table.getTableComment().substring(0, table.getTableComment().length() - 1));
        BIZ_DATA_MAP.put("className", table.getClassName());
        BIZ_DATA_MAP.put("attrClassName", table.getAttrClassName());
        BIZ_DATA_MAP.put("pathName", table.getAttrClassName().toLowerCase());
        BIZ_DATA_MAP.put("columns", table.getColumns());
        BIZ_DATA_MAP.put("hasBigDecimal", hasBigDecimal);

        VelocityContext context = new VelocityContext(BIZ_DATA_MAP);

        // 设置 velocity 资源加载器   in favor of
        Properties prop = new Properties();
        prop.put("resource.loader.file.class", "org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader");
        Velocity.init(prop);

        for (String key : SERVER_MAP.keySet()) {
            // 渲染模板
            StringWriter sw = new StringWriter();
            Template tpl = Velocity.getTemplate(key, "UTF-8");
            tpl.merge(context, sw);

            try {
                // 添加到zip
                zip.putNextEntry(new ZipEntry(String.format(SERVER_MAP.get(key), table.getModuleName(), table.getClassName())));
                IoUtil.write(sw.toString(), zip, "UTF-8");
                IoUtil.close(sw);
                zip.closeEntry();
            } catch (IOException e) {
                throw new RuntimeException("业务模板渲染失败", e);
            }
        }

        for (String key : MAPPER_MAP.keySet()) {
            // 渲染模板
            StringWriter sw = new StringWriter();
            Template tpl = Velocity.getTemplate(key, "UTF-8");
            tpl.merge(context, sw);

            try {
                // 添加到zip
                zip.putNextEntry(new ZipEntry(String.format(MAPPER_MAP.get(key), table.getClassName())));
                IoUtil.write(sw.toString(), zip, "UTF-8");
                IoUtil.close(sw);
                zip.closeEntry();
            } catch (IOException e) {
                throw new RuntimeException("MAPPER模板渲染失败", e);
            }
        }
    }

    /**
     * 列名转换成Java属性名
     */
    private static String columnToJava(String columnName) {
        if (columnName.startsWith("is_") || columnName.startsWith("IS_") || columnName.startsWith("Is_") || columnName.startsWith("iS_")) {
            columnName = columnName.replace("is_", "").replace("IS_", "").replace("Is_", "").replace("iS_", "");
        }
        return StrUtil.toCamelCase(columnName);
    }
}
